feat(recording): go2_mid360 + mid360_realsense_30 recorders#2588
feat(recording): go2_mid360 + mid360_realsense_30 recorders#2588jeff-hykin wants to merge 7 commits into
Conversation
Add memory2-based record blueprints for the Go2+Mid-360 and RealSense D435i+Mid-360 rigs: - PointlioPoseRecorder: shared Recorder base stamping each lidar frame with the latest odometry pose. - StaticTfPublisher: republishes a rig's static mount frames onto /tf on an interval (PubSubTF has no latched static tf), so they're captured in the recording's tf stream. - Go2Mid360Recorder / Mid360RealsenseRecorder + their static-transform trees and record blueprints (unitree-go2-mid360-record, mid360-realsense-record). Pygame WASD teleop on the go2 rig. - Raw Livox capture is opt-in via RECORD_PCAP=1 (reuses the existing Mid360PcapRecorder); default off. - Recording doc moved to experimental/docs/nav/map_recording/go2_mid360.md (post-processing stripped). Regenerated all_blueprints.py.
Greptile SummaryThis PR adds two new recording blueprints —
Confidence Score: 4/5The recording pipeline itself is solid, but the setup guide gives users wrong environment variable names that would leave the lidar disconnected without any obvious error. The new Python modules are well-structured and the staleness guard in PointlioPoseRecorder is correctly implemented. The documentation for the Go2+Mid-360 workflow (go2_mid360.md) instructs users to set LIDAR_IP, a variable that no module in the pipeline reads; the actual required vars are DIMOS_MID360_LIDAR_IP and DIMOS_POINTLIO_LIDAR_IP. A user following the guide verbatim would silently run with the wrong (default) lidar address on both the main and pcap-variant commands. experimental/docs/nav/map_recording/go2_mid360.md — both code blocks in step 4 use the wrong env var name. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Mid360
participant PointLio
participant PR as PointlioPoseRecorder
participant STF as StaticTfPublisher
participant DB as memory2 db
participant TF as tf stream
Mid360->>PointLio: livox_lidar / livox_imu
PointLio->>PR: pointlio_odometry → _odom_pose (stores pose + ts)
PointLio->>PR: pointlio_lidar → _lidar_pose (staleness check vs _POSE_MATCH_TOL)
PR->>DB: lidar frame + pose (or unposed if stale)
PR->>DB: companion streams (camera, go2_lidar, etc.)
loop every 1/publish_hz seconds
STF->>TF: publish static mount transforms (re-stamped)
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Mid360
participant PointLio
participant PR as PointlioPoseRecorder
participant STF as StaticTfPublisher
participant DB as memory2 db
participant TF as tf stream
Mid360->>PointLio: livox_lidar / livox_imu
PointLio->>PR: pointlio_odometry → _odom_pose (stores pose + ts)
PointLio->>PR: pointlio_lidar → _lidar_pose (staleness check vs _POSE_MATCH_TOL)
PR->>DB: lidar frame + pose (or unposed if stale)
PR->>DB: companion streams (camera, go2_lidar, etc.)
loop every 1/publish_hz seconds
STF->>TF: publish static mount transforms (re-stamped)
end
Reviews (6): Last reviewed commit: "refactor(recording): compose blueprints ..." | Re-trigger Greptile |
| config: PointlioPoseRecorderConfig | ||
|
|
||
| pointlio_odometry: In[Odometry] | ||
| pointlio_lidar: In[PointCloud2] | ||
|
|
There was a problem hiding this comment.
No staleness guard on
_lidar_pose — stale odometry silently mis-stamps frames
_lidar_pose returns _last_odom_pose unconditionally, with no check on how old that pose is. If Point-LIO temporarily drops its odometry output (degenerate geometry, topic lag, process hiccup), every subsequent lidar frame will be stamped with the last known pose rather than None. None causes the frame to be map-skipped, which is the correct fallback; a stale pose causes it to be registered at the wrong location, silently corrupting the map.
The existing PointlioRecorder uses _POSE_MATCH_TOL = 0.1 s on the raw sensor timestamps (abs(raw_ts - self._last_odom_raw_ts) <= _POSE_MATCH_TOL) to detect exactly this case. The same guard — tracking _last_odom_raw_ts and comparing it against the lidar frame's raw ts — should be applied here so the behavior is consistent.
| class Go2Mid360Recorder(PointlioPoseRecorder): | ||
| pointlio_odometry: In[Odometry] | ||
| pointlio_lidar: In[PointCloud2] | ||
| go2_lidar: In[PointCloud2] |
There was a problem hiding this comment.
These two ports are already declared on
PointlioPoseRecorder; re-declaring them here is redundant and could confuse readers into thinking the subclass owns them. The same applies to Mid360RealsenseRecorder.
| class Go2Mid360Recorder(PointlioPoseRecorder): | |
| pointlio_odometry: In[Odometry] | |
| pointlio_lidar: In[PointCloud2] | |
| go2_lidar: In[PointCloud2] | |
| class Go2Mid360Recorder(PointlioPoseRecorder): | |
| go2_lidar: In[PointCloud2] |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
|
||
| def _default_recording_dir() -> Path: | ||
| now = datetime.now() | ||
| stamp = now.strftime("%Y-%m-%d") + "_" + now.strftime("%I-%M%p").lower() + "-PST" |
There was a problem hiding this comment.
The
-PST suffix is hardcoded regardless of the machine's actual timezone. datetime.now() returns local time, so on a machine set to UTC or any other zone the label is misleading. The same issue exists in mid360_realsense_30/mid360_realsense_record.py at the same line. The simplest fix is to drop the suffix, or use datetime.now().astimezone().strftime("%Z") to pick up the real zone abbreviation.
| stamp = now.strftime("%Y-%m-%d") + "_" + now.strftime("%I-%M%p").lower() + "-PST" | |
| stamp = now.strftime("%Y-%m-%d") + "_" + now.strftime("%I-%M%p").lower() |
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## main #2588 +/- ##
==========================================
+ Coverage 69.61% 70.76% +1.15%
==========================================
Files 878 875 -3
Lines 79326 77622 -1704
Branches 7126 6893 -233
==========================================
- Hits 55220 54929 -291
+ Misses 22301 20889 -1412
+ Partials 1805 1804 -1
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 16 files with indirect coverage changes 🚀 New features to boost your workflow:
|
…z label Address Greptile review on PR #2588: - pose_recorder: guard pointlio_lidar pose-stamping with _POSE_MATCH_TOL (0.1s) on raw odom ts, matching PointlioRecorder. Stale odometry now yields an unposed (map-skipped) frame instead of mis-stamping at the last pose. - go2/realsense recorders: drop redundant pointlio_odometry/pointlio_lidar port re-declarations (inherited from PointlioPoseRecorder). - record blueprints: use datetime.now().astimezone() + %Z for the recordings dir label instead of a hardcoded -PST suffix.
mid360_realsense_30/__init__.py was license-header only; the repo forbids __init__.py under dimos/. Namespace-package import still works.
Rename mid360_realsense_record.py -> blueprints.py, drop the __main__ runner, and expose two blueprints instead of the RECORD_PCAP env toggle: - mid360_realsense_record (db only) - mid360_realsense_record_with_pcap (db + raw Mid-360 pcap) Matches the repo's blueprints.py convention (e.g. virtual_mid360). Regenerated all_blueprints.py.
…s consts Let each module self-configure its lidar IP from its own env var (DIMOS_MID360_LIDAR_IP for Mid360/pcap, DIMOS_POINTLIO_LIDAR_IP for Point-LIO) instead of a blueprint-level LIDAR_IP env + hardcoded default fanned out to all of them. Inline n_workers. Applies to both the realsense blueprints and the go2 record blueprint.
Build the with-pcap variant by nesting the base blueprint (autoconnect(base, Mid360PcapRecorder)) instead of spreading a shared *_modules list. autoconnect merges the nested blueprint's modules, remappings, and global_config (n_workers) — verified. Same nesting for the go2 RECORD_PCAP opt-in.
go2_mid360andmid360_realsenserecording blueprints